package com.gitee.wsl.common.ui.ext.color

import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.lerp
import kotlin.math.pow


/**
 * Interpolates this color towards the `other` color. The closer `thisLikeness` is to 0.0,
 * the closer the resulting color will be to the `other` color.
 */
fun Color.interpolateTowards(other: Color, thisLikeness: Float): Color {
    require((thisLikeness >= 0.0f) && (thisLikeness <= 1.0f)) {
        "Color likeness should be in 0.0-1.0 range [is $thisLikeness]"
    }
    val alpha1: Float = this.alpha
    val alpha2: Float = other.alpha
    val r = getInterpolatedChannelValue(this.red, other.red, thisLikeness)
    val g = getInterpolatedChannelValue(this.green, other.green, thisLikeness)
    val b = getInterpolatedChannelValue(this.blue, other.blue, thisLikeness)
    val a = (if (alpha1 == alpha2) alpha1 else
        thisLikeness * alpha1 + (1.0f - thisLikeness) * alpha2).coerceIn(0.0f, 1.0f)
    return Color(r, g, b, a, this.colorSpace)
}

fun Color.interpolateTowardsAsRGB(other: Color, thisLikeness: Float): Int {
    require((thisLikeness >= 0.0f) && (thisLikeness <= 1.0f)) {
        "Color likeness should be in 0.0-1.0 range [is $thisLikeness]"
    }
    val alpha1: Float = this.alpha
    val alpha2: Float = other.alpha
    val r = getInterpolatedChannelValue(this.red, other.red, thisLikeness)
    val g = getInterpolatedChannelValue(this.green, other.green, thisLikeness)
    val b = getInterpolatedChannelValue(this.blue, other.blue, thisLikeness)
    val a = (if (alpha1 == alpha2) alpha1 else
        thisLikeness * alpha1 + (1.0f - thisLikeness) * alpha2).coerceIn(0.0f, 1.0f)

    return ((a * 255.0f + 0.5f).toInt() shl 24) or
            ((r * 255.0f + 0.5f).toInt() shl 16) or
            ((g * 255.0f + 0.5f).toInt() shl 8) or
            (b * 255.0f + 0.5f).toInt()
}

private fun getInterpolatedChannelValue(value1: Float, value2: Float, value1Likeness: Float): Float {
    if (value1 == value2) {
        return value1
    }
    if (value1Likeness == 1.0f) {
        return value1
    }
    if (value1Likeness == 0.0f) {
        return value2
    }

    // Step 1 - convert channel from electro to optical
    val optical1 = EOCF_sRGB(value1)
    val optical2 = EOCF_sRGB(value2)

    // Step 2 - interpolate
    val interpolatedOptical = value1Likeness * optical1 +
            (1.0f - value1Likeness) * optical2

    // Step 3 - convert interpolated from optical to electro
    val interpolatedElectro = OECF_sRGB(interpolatedOptical)

    // Step 4 - convert to 0..1 range
    // using some interpolation values (such as 0.29 from issue 401)
    // results in an incorrect final value without Math.round.
    var result = interpolatedElectro
    if (result < 0.0f) {
        result = 0.0f
    }
    if (result > 1.0f) {
        result = 1.0f
    }
    return result
}

// Electro-optical conversion function for the sRGB color space
// Takes a linear sRGB value and converts it to a gamma-encoded sRGB value
private fun EOCF_sRGB(srgb: Float): Float {
    // IEC 61966-2-1:1999
    return if (srgb <= 0.04045f) srgb / 12.92f else ((srgb + 0.055f) / 1.055f).pow(2.4f)
}

// Opto-electronic conversion function for the sRGB color space
// Takes a gamma-encoded sRGB value and converts it to a linear sRGB value
private fun OECF_sRGB(linear: Float): Float {
    // IEC 61966-2-1:1999
    return if (linear <= 0.0031308f) linear * 12.92f else
        linear.pow(1.0f / 2.4f) * 1.055f - 0.055f
}


fun getColorByPosition(gradientItems: List<Color>, position: Float): Color {
    if (position <= 0) return gradientItems.first()
    if (position >= 1) return gradientItems.last()
    val step = 1f / (gradientItems.size - 1)
    for (i in 0 until gradientItems.size - 1) {
        val startStop = i * step
        val endStop = (i + 1) * step
        if (position in startStop..endStop) {
            val startColor = gradientItems[i]
            val endColor = gradientItems[i + 1]

            val fraction = (position - startStop) / (endStop - startStop)
            return lerp(startColor, endColor, fraction)
        }
    }
    return gradientItems.first()
}